package Pong;

import java.awt.*;
import javax.swing.*;

import mote.Mote;
import mote.MoteBridge;
import mote.MoteCollection;

/**
 * Pong class contains code for game of Pong.
 * @author 
 */
public class Pong {
	
	JFrame frame;
	PongComponent pc;
	
	static final double BOTTOM_SCALE_BIAS = .9;
	static final double TOP_SCALE_BIAS = .95;
	static final int WIDTH = 700;
	static final int HEIGHT = 400;
	static final int STEPTIME = 12;
	static final int STEPSIZE = 10;
	static final String GAMENAME = "Projector Pong";
	static final int BALL_SIZE = 10;
	static final Color BALL_COLOR = Color.red;
	static final int MAX_POSITION = 600;
	static final int READINGS_SAMPLED = 10;
	
	public static void main(String[] args)
	{
		new Pong();
	}
	
	// Top and Bottom reading for each player mote
	// Used for calibration over game light gradient.
	int top1, bottom1;
	int top2, bottom2;
	
	Pong()
	{
		frame = new JFrame(GAMENAME);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		pc = new PongComponent();
		
		frame.add(pc);
		
		frame.pack();
		
		frame.setSize(1000, HEIGHT + 32);
		
		frame.setVisible(true);
		frame.repaint();
		
		MoteCollection mc = new MoteCollection();
		
		MoteBridge mb = new MoteBridge(mc);
		
		try {
			// Wait for total 2 motes
			while(mc.Count() < 2)
			{
				Thread.sleep(100);
			}
			
			Mote m1 = mc.Motes().get(0);
			Mote m2 = mc.Motes().get(1);
			
			// Set LEDs of both motes with given color value
			mb.setLEDs(m1.GetId(), (byte)1);
			mb.setLEDs(m2.GetId(), (byte)4);
			
			mb.setSamplingInterval(m1.GetId(), 2);
			mb.setSamplingInterval(m2.GetId(), 2);
			
			System.out.println("Place the motes at the top and press any key to continue");
			System.in.read();
			top1 = (int) m1.AverageReading(READINGS_SAMPLED);
			top2 = (int) m2.AverageReading(READINGS_SAMPLED);
			
			System.out.println("Top1 set at: " + top1);
			System.out.println("Top2 set at: " + top2);
			System.out.println("Place the motes at the bottom and press any key to continue");
			System.in.read();
			
			bottom1 = (int) m1.AverageReading(READINGS_SAMPLED);
			bottom2 = (int) m2.AverageReading(READINGS_SAMPLED);
			System.out.println("Bottom1 set at: " + bottom1);
			System.out.println("Bottom2 set at: " + bottom2);

			while(true)
			{
				Thread.sleep(STEPTIME);
				
				int pos1 = calculatePosition(m1.AverageReading(READINGS_SAMPLED), top1, bottom1);
				int pos2 = calculatePosition(m2.AverageReading(READINGS_SAMPLED), top2, bottom2);
				//System.out.println("LightReading1: " + m.Reading() + " position: " + pos1);
				pc.setPaddle1(pos1);
				pc.setPaddle2(pos2);
				frame.repaint();
			}
		} catch (Exception e) {
			System.out.println(e);
		}
	}
	
	/**
	 * Calculate vertical position of the paddle given its mote reading
	 * @param reading Reading from given mote over light gradient
	 * @param top Value of top set for this mote while doing calibration
	 * @param bottom Value of bottom set for this mote while doing calibration
	 * @return Vertical location of the given paddle
	 */
	private int calculatePosition(double reading, int top, int bottom) {
		double val = ((1.0 - (((double)(reading-bottom))/((double)(top-bottom)))));
		
		//scale val by a progressive bias
		//double bias = ((1.0-val)*TOP_SCALE_BIAS+val*BOTTOM_SCALE_BIAS);
		//System.out.println("Bias: "+bias);
		//val = val-(1-bias);
		if(val<0.0)
			val = 0.0;
		else if(val>1.0)
			val = 1.0;
		
		return (int)(val*MAX_POSITION);
	}
	
	/**
	 * This class describes GUI component of the game.
	 */
	public class PongComponent extends JComponent
	{
		static final int PADDLE_RAD = 40;
		static final int PADDLE_WIDTH = 10;
		static final int LIGHT_GRAY_POINT = 400;
		static final int GRAD_WIDTH = 150;
		int pos = 0;
		Ball ball;
		int ballRad;
		int paddle1, paddle2;
		int score1, score2;
		
		PongComponent()
		{
			ball = new Ball();
			ballRad = Pong.BALL_SIZE / 2;
			score1 = 0;
			score2 = 0;
		}
		
		public void paint(Graphics g)
		{
			Graphics2D g2d = (Graphics2D)g;
			
			// paint a background
			g2d.setPaint(Color.lightGray);
			g2d.fillRect(GRAD_WIDTH, 0, Pong.WIDTH, Pong.HEIGHT);
			
			// check for bounces
			int newY = ball.y + ball.dy;
			int newX = ball.x + ball.dx;
			if(newY + ballRad > Pong.HEIGHT || newY - ballRad  < 0)
			{
				ball.dy = -ball.dy;
			}
			if(newX + ballRad > Pong.WIDTH + GRAD_WIDTH - PADDLE_WIDTH)
			{
				double intersectX = Pong.WIDTH + GRAD_WIDTH - PADDLE_WIDTH;
				double intersectY = (intersectX-ball.x)/(newX-ball.x) * ball.dy +ball.y;
				
				// check if ball intersects right paddle
				if(intersectY > paddle2 - PADDLE_RAD
				 && intersectY < paddle2 + PADDLE_RAD)
				{
					/*
					// bounce logic
					double diff = ((double)(ball.y - paddle2))/PADDLE_RAD;
					double sign = (diff > 0) ? 1 : -1;
					ball.dy = (int)(sign*Math.max(diff*STEPSIZE, 0.6*STEPSIZE));
					//ball.dx = -(int)Math.min(STEPSIZE - ball.dy, 0.4*STEPSIZE);
					ball.dx = -ball.dx;
					*/
					
					
					System.out.println("intersection at: ("+intersectX+","+intersectY+")");
					
					double paddleX = PADDLE_WIDTH + 200;
					double paddleY = intersectY - paddle2;
					
					System.out.println("adjusted to paddle: ("+paddleX+","+paddleY+")");
					
					double normalY = Math.sin(paddleX/paddleY);
					double normalX = Math.cos(paddleX/paddleY);
					
					System.out.println("normal: ("+normalX+","+normalY+")");
					
					double dot = (newX-ball.x)*normalX + (newY-ball.y)*normalY;
					
					//ball.dx = ball.dx + (int)((-dot*2)*normalX);
					ball.dy = ball.dy + (int)((-dot*2)*normalY);
					
					ball.dx = -ball.dx;
					
					/*if(ball.dx>-2){
						ball.dx=-2;
					}*/
					
					System.out.println("Ball direction: ("+ball.dx+","+ball.dy+")");
					
					/*
					double backVectorY = paddle2 - newY;
					ball.dx = -ball.dx;
					ball.dy = (int)(backVectorY*ball.dy);
					*/
				}
				else
				{
					score1++;
					ball.reset();
				}
			}
			
			if(newX - ballRad < GRAD_WIDTH + PADDLE_WIDTH)
			{
				double intersectX = GRAD_WIDTH + PADDLE_WIDTH;
				double intersectY = (intersectX-ball.x)/(newX-ball.x) * ball.dy +ball.y;
				
				
				// check if ball intersects left paddle
				if(intersectY > paddle1 - PADDLE_RAD
				 && intersectY < paddle1 + PADDLE_RAD)
				{
					/*// bounce logic
					double diff = ((double)(ball.y - paddle1))/PADDLE_RAD;
					double sign = (diff > 0) ? 1 : -1;
					ball.dy = (int)(sign*Math.max(diff*STEPSIZE, 0.6*STEPSIZE));
					//ball.dx = (int)Math.min(STEPSIZE - ball.dy, 0.4*STEPSIZE);
					ball.dx = -ball.dx;
					*/
					
					System.out.println("intersection at: ("+intersectX+","+intersectY+")");
					
					double paddleX = -PADDLE_WIDTH - 200;
					double paddleY = intersectY - paddle1;
					
					System.out.println("adjusted to paddle: ("+paddleX+","+paddleY+")");
					
					double normalY = Math.sin(paddleX/paddleY);
					double normalX = Math.cos(paddleX/paddleY);
					
					System.out.println("normal: ("+normalX+","+normalY+")");
					
					double dot = (newX-ball.x)*normalX + (newY-ball.y)*normalY;
					
					//ball.dx = ball.dx + (int)((-dot*2)*normalX);
					ball.dy = ball.dy + (int)((-dot*2)*normalY);
					ball.dx = -ball.dx;
					
					System.out.println("Ball direction: ("+ball.dx+","+ball.dy+")");
					
					/*
					if(ball.dx<2){
						ball.dx=2;
					}
					*/
					
					/*
					double backVectorY = paddle1 - newY;
					ball.dx = -(ball.dx * PADDLE_WIDTH);
					ball.dy = (int)(backVectorY*ball.dy);
					*/
				}
				else
				{
					score2++;
					ball.reset();
				}
			}
			
			ball.move();
			
			g2d.setPaint(BALL_COLOR);
			g2d.fillOval(ball.x-ballRad, ball.y-ballRad, BALL_SIZE, BALL_SIZE);
			
			
			g2d.setPaint(Color.black);
			g2d.fillRect(GRAD_WIDTH, paddle1 - PADDLE_RAD, PADDLE_WIDTH, PADDLE_RAD*2);
			g2d.fillRect(Pong.WIDTH + GRAD_WIDTH - PADDLE_WIDTH, paddle2 - PADDLE_RAD, PADDLE_WIDTH, PADDLE_RAD*2);
			
			GradientPaint grad1 = new GradientPaint(0, 0, Color.white, 0, LIGHT_GRAY_POINT, Color.gray);
			GradientPaint grad2 = new GradientPaint(0, LIGHT_GRAY_POINT, Color.gray, 0, getHeight() + 200, Color.black);
			g2d.setPaint(grad1);
			g2d.fillRect(0, 0, GRAD_WIDTH, LIGHT_GRAY_POINT);
			g2d.fillRect(Pong.WIDTH+GRAD_WIDTH,0,GRAD_WIDTH, LIGHT_GRAY_POINT);
			
			g2d.setPaint(grad2);
			g2d.fillRect(0, LIGHT_GRAY_POINT, GRAD_WIDTH, Pong.HEIGHT);
			g2d.fillRect(Pong.WIDTH+GRAD_WIDTH, LIGHT_GRAY_POINT, GRAD_WIDTH, Pong.HEIGHT - LIGHT_GRAY_POINT);
			
			g2d.setPaint(Color.black);
			g2d.drawString(new Integer(score1).toString(), 200, 100);
			g2d.drawString(new Integer(score2).toString(), 700, 100);
		}
		
		/**
		 * Put paddle 1 on desired height.
		 * @param height Height at which paddle is to put.
		 */
		public void setPaddle1(int height)
		{
			paddle1 = height;
		}
		
		/**
		 * Put paddle 2 on desired height.
		 * @param height Height at which paddle is to put.
		 */
		public void setPaddle2(int height)
		{
			paddle2 = height;
		}
	}
	
	/**
	 * A private class, used to define a ball dimention and velocity 
	 * components.
	 */
	private class Ball
	{
		public int x, y, dx, dy;
		Ball()
		{
			reset();
		}
		
		/**
		 * Reset the ball location in the game and start with moving
		 * ball in a random direction.
		 */
		void reset()
		{
			x = PongComponent.GRAD_WIDTH + (Pong.WIDTH / 2);
			y = Pong.HEIGHT / 2;
			dx = -Pong.STEPSIZE / 2;
			if(Math.random() > 0.5)
			{
				dx = -dx;
			}
			dy = dx;
		}
		
		/*
		 * Make the next move based on the step value.
		 */
		void move()
		{
			x += dx;
			y += dy;
		}
		
	}
	
}
